/**!
 * Copyright (c) [2021] [Tenny]
   [pino-smart] is licensed under Mulan PSL v2.
   You can use this software according to the terms and conditions of the Mulan PSL v2. 
   You may obtain a copy of Mulan PSL v2 at:
            http://license.coscl.org.cn/MulanPSL2 
   THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.  
   See the Mulan PSL v2 for more details.
 */
import colors = require('terminal-simple-colors');

const nl = '\n';

/**
 * 将字符串解析为 JSON 格式
 * @param {string} input 待解析为JSON格式的字符串
 * @returns {Object} 解析后的结果；如果解析成功，则包含 value 字段为解析成功的值；否则 err 表示失败错误
 */
function jsonParse (input: string) {
  try {
    return { value: JSON.parse(input) };
  } catch (error) {
    return { err: error };
  }
}

function isObject (log: object) {
  return Object.prototype.toString.apply(log) === '[object object]';
}

/**
 * 不足位数, 前位补 0
 * @param s 日期数字
 * @param l 截取位数
 * @returns {string}  补0后的日期数字
 */
 function p (s: number|string, l = 2) {
  /*
   * 由于年份最多为4为，所以前面先添3个0
   * slice() 从后开始提前字符串
   */
  return ('000' + s).slice(l * -1);
}

/**
 * JSON 日志格式化
 */
function PinoSmart () {
  return pretty;
  
  /**
   * 格式化日志
   * @param {any} inputData 识别到的日志数据
   * @returns 格式化后的日志返回
   */
  function pretty (inputData: any) {
    let log;
    if (typeof inputData === 'string') {
      let parsedData = jsonParse(inputData);
      if (!parsedData.value || parsedData.err) {
        return inputData + nl;
      }
      log = parsedData.value;
    } else if (isObject(inputData)) {
      log = inputData;
    } else {
      return inputData + nl;
    }
    if (!log.level) return inputData + nl;
    if (typeof log.level === 'number') convertNumberLevel(log);
    // 保留每一行日志的每一个数据元
    let result = [];
    result.push(formatTime(log.time));
    result.push(formatLevel(log.level));
    if (log.name) {
      result.push(formatName(log.name));
    }

    // 格式化请求日志
    let req = log.req;
    let res = log.res;
    let statusCode = res ? res.statusCode : log.statusCode;
    let responseTime = log.responseTime;
    let method = req ? req.method : log.method;
    let contentLength = log.contentLength;
    let url = req ? req.url : log.url;
    let stack, err;
    if (log.level === 'fatal' || log.level === 'error') {
      stack = log.stack || (log.err && log.err.stack);
      if (log.err && Object.keys(log.err).find(key => key !== 'stack')) {
        err = log.err;
      }
    }
    if (method) result.push(whiteInfo(method));
    if (statusCode) result.push(whiteInfo(statusCode));
    if (url) result.push(whiteInfo(url));
    if (contentLength) result.push(whiteInfo(contentLength));
    if (responseTime) result.push(formatResponseTime(responseTime));
    if (stack) result.push(formatStack(stack));
    if (err) result.push(formatError(err));

    if (log.level !== 'error') result.push(formatMessage(log));
    return result.filter(val => (val != null && val.trim().length > 0)).join(' ') + '\n';
  }

  function formatStack (stack: string) {
    return colors.red(nl + stack);
  }

  function formatError (err: object) {
    return colors.red(nl + JSON.stringify({ err }, null, 2));
  }

  function formatResponseTime (responseTime: string) {
    return colors.brightBlack(parseInt(responseTime, 10) + 'ms');
  }

  function whiteInfo (text: string) {
    return colors.white(text);
  }

  function formatTime (time: number) {
    let date = new Date(time);
    let hours = p(date.getHours().toString());
    let minutes = p(date.getMinutes().toString());
    let seconds = p(date.getSeconds().toString());
    return colors.brightBlack(hours + ':' + minutes + ':' + seconds);
  }

  function formatLevel (level: string) {
    return `[${colors.brightGreen(level.toUpperCase())}]`;
  }

  function formatName (name: string) {
    return colors.blue(name);
  }

  function formatMessage (log: any) {
    let msg = log.msg || log.sql;
    let pretty = colors.green(msg);
    if (log.level === 'error') pretty = colors.red(msg);
    if (log.level === 'trace') pretty = colors.white(msg);
    if (log.level === 'warn') pretty = colors.yellow(msg);
    if (log.level === 'debug') pretty = colors.magenta(msg);
    if (log.level === 'fatal') pretty = colors.cyan(msg);
    return colors.magenta('-- ') + pretty;
  }

  function convertNumberLevel (log: any) {
    if (log.level === 10) log.level = 'trace';
    if (log.level === 20) log.level = 'debug';
    if (log.level === 30) log.level = 'info';
    if (log.level === 40) log.level = 'warn';
    if (log.level === 50) log.level = 'error';
    if (log.level === 60) log.level = 'fatal';
  }
}

export = PinoSmart;